package net.minject;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Provider;

import net.minject.reflection.Reflector;
import net.minject.reflection.ReflectorImpl;

public class Minject {

    public static final Class<? extends Annotation> INJECT_ANNOTATION = Inject.class;
    private final Reflector reflector;

    public Minject() {
        this(new ReflectorImpl());
    }

    public Minject(Reflector reflector) {
        this.reflector = reflector;
    }

    public <T> T getInstance(Class<T> clazz) {
        Constructor<T> ctor = reflector.findConstructorAnnnotatedBy(clazz, INJECT_ANNOTATION);
        List<Field> fields = reflector.findFieldsAnnotatedBy(clazz, INJECT_ANNOTATION);
        List<Method> methods = reflector.findMethodsAnnotatedBy(clazz, INJECT_ANNOTATION);
        T instance = null;
        if (ctor == null) {
            instance = reflector.construct(clazz);
        } else {
            Class<?>[] parameterTypes = ctor.getParameterTypes();
            Object[] parameterInstances = new Object[parameterTypes.length];
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                Object inst = getInstance(parameterType);
                parameterInstances[i++] = inst;
            }
            instance = reflector.construct(clazz, parameterInstances);
        }
        for (final Field field : fields) {
            if (isProvider(field)) {
                @SuppressWarnings("rawtypes")
                Provider provider = new Provider() {
                    public Object get() {
                        return getInstance(getProviderType(field));
                    }
                };
                reflector.setField(field, instance, provider);
            } else {
                reflector.setField(field, instance, getInstance(field.getType()));
            }
        }
        for (Method method : methods) {
            Class<?>[] methodParameterTypes = method.getParameterTypes();
            Object[] methodArgs = new Object[methodParameterTypes.length];
            for (int i = 0; i < methodArgs.length; i++) {
                methodArgs[i] = getInstance(methodParameterTypes[i]);
            }
            reflector.invokeMethod(method, instance, methodArgs);
        }
        return instance;
    }

    private boolean isProvider(final Field field) {
        return Provider.class.isAssignableFrom(field.getType());
    }

    private Class<?> getProviderType(Field field) {
        ParameterizedType pt = (ParameterizedType) field.getGenericType();
        Type[] types = pt.getActualTypeArguments();
        System.out.println(types[0]);
        return (Class<?>) types[0];
    }

}
